#include <assert.h>
#include <string.h>
#include "ClassFileConstant.h"
#include "ConstantPool.h"
#include "SimpleIo.h"
#include "Log.h"

ConstantPool::ConstantPool()
{
  ClassFileConstant * c=new ClassFileConstant(0xff,"dummy");
  m_constants.push_back(c);
}

ClassFileConstant * ConstantPool::getAt(int index)
{
  return m_constants[index];
}

u2 ConstantPool::addClass(const char * cstring)
{
  u2 index;

  index=getClass(cstring);

  if(index==0) {
    ClassFileConstant * k=new ClassFileConstantClass(this,cstring);
    m_constants.push_back(k);
    index=m_constants.size()-1;
  }

  return index;
}

u2 ConstantPool::getFieldRef(u2 class_index, u2 NameAndType_index)
{
  ClassFileConstant * k;
  ClassFileConstantFieldRef * kk;
  for(u2 i=0;i<m_constants.size();i++) {
    k=m_constants[i];
    if(k->tag==CONSTANT_Fieldref) {
      kk=(ClassFileConstantFieldRef*)k;
      if(kk->class_index==class_index &&
         kk->name_and_type_index==NameAndType_index)
        return i;
    }
  }
  return 0;
}

u2 ConstantPool::getUtf8(const char * cstring)
{
  ClassFileConstant * k;
  ClassFileConstantUtf8 * kk;

  u2 length=strlen(cstring);
  for(u2 i=0;i<m_constants.size();i++) {
    k=m_constants[i];
    if(k->tag==CONSTANT_Utf8) {
      kk=(ClassFileConstantUtf8*)k;
      if(kk->length==length &&
          memcmp(kk->string,cstring,length)==0)
        return i;
    }
  }
  return 0;
}

u2 ConstantPool::getString(u2 utf8_index)
{
  ClassFileConstant * k;
  ClassFileConstantString * kk;
  for(u2 i=0;i<m_constants.size();i++) {
    k=m_constants[i];
    if(k->tag==CONSTANT_String) {
      kk=(ClassFileConstantString*)k;
      if(kk->string_index==utf8_index)
        return i;
    }
  }
  return 0;
}

u2 ConstantPool::getMethodRef(u2 class_index, u2 NameAndType_index)
{
  ClassFileConstant * k;
  ClassFileConstantMethodRef * kk;
  for(u2 i=0;i<m_constants.size();i++) {
    k=m_constants[i];
    if(k->tag==CONSTANT_Methodref) {
      kk=(ClassFileConstantMethodRef*)k;
      if(kk->class_index==class_index &&
         kk->name_and_type_index==NameAndType_index)
        return i;
    }
  }
  return 0;
}

u2 ConstantPool::getNameAndType(const char * name, const char * signature)
{
  ClassFileConstant * k;
  ClassFileConstantNameAndType * kk;
  for(u2 i=0;i<m_constants.size();i++) {
    k=m_constants[i];
    if(k->tag==CONSTANT_NameAndType) {
      kk=(ClassFileConstantNameAndType*)k;
      const char * found_name;
      const char * found_signature;
      found_name=getCString(kk->name_index);
      found_signature=getCString(kk->signature_index);
      if(strcmp(found_name,name)==0 &&
         strcmp(found_signature,signature)==0)
        return i;
    }
  }
  return 0;
}

u2 ConstantPool::addNameAndType(const char * name, const char * signature)
{
  // TODO: check if class already exists!!!
  u2 index;
  index=getNameAndType(name,signature);
  if(index==0) {
    ClassFileConstant * k=new ClassFileConstantNameAndType(this,name,signature);
    m_constants.push_back(k);
    index=m_constants.size()-1;
  }

  return index; //m_constants.size()-1;
  
}

u2 ConstantPool::addFieldRef(const char * class_name, 
    const char * field_name,
    const char * signature)
{
  u2 class_index;
  u2 name_and_type_index;
  class_index=addClass(class_name);
  name_and_type_index=addNameAndType(field_name,signature);


  u2 index;

  index=getFieldRef(class_index,name_and_type_index);

  if(index==0) {
    ClassFileConstant * k=
      new ClassFileConstantFieldRef(class_index,name_and_type_index);
    m_constants.push_back(k);
    index=m_constants.size()-1;
  }
  return index;
}


u2 ConstantPool::addMethodRef(const char * class_name, 
    const char * name,
    const char * signature)
{
  u2 class_index;
  u2 name_and_type_index;
  class_index=addClass(class_name);
  name_and_type_index=addNameAndType(name,signature);

  u2 index;

  index=getMethodRef(class_index,name_and_type_index);

  if(index==0) {
    ClassFileConstant * k=
      new ClassFileConstantMethodRef(class_index,name_and_type_index);
    m_constants.push_back(k);
    index=m_constants.size()-1;
  }

  return index;
}

u2 ConstantPool::addString(const char * cstring)
{
  u2 utf8_index=addUtf8(cstring);

  u2 index;

  index=getString(utf8_index);

  if(index==0) {
    ClassFileConstant * k=
      new ClassFileConstantString(utf8_index);
    m_constants.push_back(k);
    index=m_constants.size()-1;
  }
  
  return index;
}

u2 ConstantPool::addDouble(double value)
{
  ClassFileConstant * k=
    new ClassFileConstantDouble(value);
  m_constants.push_back(k);

  ClassFileConstant * kd=new ClassFileConstant(0xff,"dummy");
  m_constants.push_back(kd);
  return m_constants.size()-2;
}


u2 ConstantPool::addUtf8(const char * cstring)
{
  u2 index;

  index=getUtf8(cstring);

  if(index==0) {
    ClassFileConstant * k=new ClassFileConstantUtf8(cstring);
    m_constants.push_back(k);
    index=m_constants.size()-1;
  }

  return index;
}

void ConstantPool::save(FILE *f)
{
  writeUint16(f,m_constants.size());
  for(u2 i=0;i<m_constants.size();i++) {
    LOG_SAVE("at %ld saving constant %d: ",ftell(f),i);
#ifdef ENABLE_LOG_SAVE
    m_constants[i]->dump(this,true);
#endif
    LOG_SAVE("\n");
    m_constants[i]->save(f);
  }
}

u2 ConstantPool::addFromFile(FILE *f)
{
  u1 tag;
  u2 u2_r1,u2_r2;
  u2 buffer[1024];

  ClassFileConstant * c;

  tag=readUint8(f);
   
  switch(tag) {
    case CONSTANT_String:
      c=new ClassFileConstantString(f);
      break;

    case CONSTANT_Class:
      u2_r1=readUint16(f);
      c=new ClassFileConstantClass(u2_r1);
      break;
      
    case CONSTANT_Unicode:
      u2_r1=readUint16(f);
      assert(u2_r1<=512);
      readUint16Array(u2_r1,buffer,f);
      c=new ClassFileConstantUnicode(u2_r1,buffer);
      break;

   case CONSTANT_Methodref:
      u2_r1=readUint16(f);
      u2_r2=readUint16(f);
      c=new ClassFileConstantMethodRef(u2_r1,u2_r2);
      break;

   case CONSTANT_Utf8:
      c=new ClassFileConstantUtf8(f);
      break;

   case CONSTANT_NameAndType:
      c=new ClassFileConstantNameAndType(f);
      break;

   case CONSTANT_Fieldref:
      c=new ClassFileConstantFieldRef(f);
      break;
   
   case CONSTANT_Integer:
      c=new ClassFileConstantInteger(f);
      break;

   case CONSTANT_Double:
      c=new ClassFileConstantDouble(f);
      break;

    default:
      assert(0);
  }
  m_constants.push_back(c);

  if(c->usedSpots()==2) {
    //m_constants.push_back(c);
    m_constants.push_back(m_constants[0]);
  } else {
    assert(c->usedSpots()==1);
  }

  return c->usedSpots();
}

bool ConstantPool::isStringAt(u2 index)
{
  return m_constants[index]->tag==CONSTANT_String;
}

bool ConstantPool::isUtf8At(u2 index)
{
  assert(index<m_constants.size());
  return m_constants[index]->tag==CONSTANT_Utf8;
}

bool ConstantPool::isClassAt(u2 index)
{
  assert(index<m_constants.size());
  return m_constants[index]->tag==CONSTANT_Class;
}

#if 0
u2 ConstantPool::getFieldRef(const char * klass, const char * nameAndType)
{
  u2 i;
  u2 name_index;
  for(i=0;i<m_constants.size();i++) {
    if(m_constants[i]->tag==CONSTANT_Fieldref) {
      klass_index=((ClassFileConstantFieldRef*)m_constants[i])->name_index;
      =((ClassFileConstantFieldRef*)m_constants[i])->name_index;
      assert(m_constants[name_index]->tag==CONSTANT_Utf8);
      if(strcmp(cstring,
          ((ClassFileConstantUtf8*)m_constants[name_index])->string)==0)
        return i;
    }
  }
  return 0;
}
#endif


u2 ConstantPool::getClass(const char * cstring)
{
  u2 i;
  u2 name_index;
  for(i=0;i<m_constants.size();i++) {
    if(m_constants[i]->tag==CONSTANT_Class) {
      name_index=((ClassFileConstantClass*)m_constants[i])->name_index;
      assert(m_constants[name_index]->tag==CONSTANT_Utf8);
      if(strcmp(cstring,
          ((ClassFileConstantUtf8*)m_constants[name_index])->string)==0)
        return i;
    }
  }
  return 0;
}

const char * ConstantPool::getCString(u2 index)
{
  assert(isUtf8At(index));
  return ((ClassFileConstantUtf8*)m_constants[index])->getCString();
}


